from matplotlib.axes import Axes
from matplotlib.gridspec import SubplotSpec
from cartopy.mpl.geoaxes import GeoAxes
from cartopy import crs as ccrs
from typing import Literal, Union
from .length_units import LenType, Pt, RelX, RelY, as_inch, as_unit_value
from matplotlib.quiver import Quiver
import numpy as np
from matplotlib.patches import PathPatch
from matplotlib.path import Path
from dataclasses import dataclass, field
from xarray import DataArray
from ._quiver import LocType

@dataclass
class MapConfig:
    """绘制地图底图的配置类，用于 `AxesTools.map_base()` 或
    `SubplotParams.map_base()` 方法。"""

    coastline: bool = True
    """是否绘制海岸线"""
    coastline_width: float = 0.5
    """海岸线的宽度，单位为 pt"""
    coastline_color: str = 'k'
    """海岸线的颜色"""
    coastline_kwargs: dict = field(default_factory=dict)
    """海岸线的其他参数"""

    fill_land: bool = False
    """是否填充陆地"""
    land_color: str = 'lightgray'
    """陆地的颜色"""

    gridline: bool = True
    """是否绘制网格线"""
    gridline_style: str = '--'
    """网格线的样式"""
    gridline_width: float = 0.35
    """网格线的宽度，单位为 pt"""
    gridline_color: str = 'gray'
    """网格线的颜色"""
    gridline_zorder: int = 90
    """网格线的图层位置"""
    gridline_kwargs: dict = field(default_factory=dict)
    """网格线的其他参数"""

    lon_major_interval: float = 20
    """经度主刻度的间隔，单位为度"""
    lon_minor_interval: float = 10
    """经度次刻度的间隔，单位为度"""
    lat_major_interval: float = 20
    """纬度主刻度的间隔，单位为度"""
    lat_minor_interval: float = 10
    """纬度次刻度的间隔，单位为度"""


class AxesTools:

    def __init__(self, ax: Axes):
        self.ax = ax
        self._figure = ax.figure
        self._is_geo_axes = isinstance(ax, GeoAxes)

        self._bbox = ax.get_position()
        self.ax_width = RelX(self._bbox.width, self._figure)
        self.ax_height = RelY(self._bbox.height, self._figure)

    def label(self, text: str, loc: Literal['lt', 'lb', 'rt', 'rb']='lt',
              offset_x: LenType=Pt(1), offset_y: LenType=Pt(1),
              background_color: Union[bool, str]= 'white',
              background_pad: float = 0.3, zorder=100, **kwargs):
        """为 Axes 添加一个在角落的标签，可用于给子图编号。

        Add a label to the corner of the Axes, which can be used to number
        the subplots.

        Parameters
        ----------
        text : str
            标签内容
        loc : Literal['lt', 'lb', 'rt', 'rb']
            标签位置，lt 左上，lb 左下，rt 右上，rb 右下

            label position combination of: left/right, top/bottom
        offset_x : LenType, optional
            x方向上距离 Axes 边框的偏移, 默认单位为英寸。

            x direction offset from the edge of the Axes, the default unit is inch.
        offset_y : LenType, optional
            y方向上距离 Axes 边框的偏移, 默认单位为英寸。

            y direction offset from the edge of the Axes, the default unit is inch.
        background_color : bool | any, optional
            标签的背景颜色，如果不需要背景，设置为 False，默认为白色。

            the background color of the label, and if you don't need a background,
            set it to False.
        background_pad : float, optional
            背景的内边距，即背景的边缘到文字的距离, 默认为 0.3。

            the padding of the background border to the text, by default 0.3
        zorder : int, optional
            图层位置, by default 100

            zorder of the label, by default 100

        """

        # 转为 Axes 的相对单位
        offset_x, offset_y = as_inch(self._figure, offset_x, offset_y)
        offset_x = offset_x / self.ax_width
        offset_y = offset_y / self.ax_height

        match loc:
            case "lt":
                x = offset_x
                y = 1 - offset_y
                ha = 'left'
                va = 'top'
            case "lb":
                x = offset_x
                y = offset_y
                ha = 'left'
                va = 'bottom'
            case "rt":
                x = 1 - offset_x
                y = 1 - offset_y
                ha = 'right'
                va = 'top'
            case "rb":
                x = 1 - offset_x
                y = offset_y
                ha = 'right'
                va = 'bottom'
            case _:
                raise ValueError(f"Unknown loc: {loc}")

        if background_color:
            return self.ax.text(x, y, text, ha=ha, va=va, transform=self.ax.transAxes,
                bbox=dict(facecolor=background_color, edgecolor='none',
                          pad=background_pad), zorder=zorder, **kwargs)
        else:
            return self.ax.text(x, y, text, ha=ha, va=va, transform=self.ax.transAxes,
                zorder=zorder, **kwargs)


    def quiver_by_size(self, *args, length_pre_unit: LenType=Pt(10),
                       width: LenType=Pt(1), **kwargs) -> Quiver:
        """使用指定的长度单位来绘制箭头图，即设置 1 m/s 的风矢量长多少像素。
        可以使用 `length_pre_unit` 设置数据每单位对应的实际长度。
        例如数据 u=1, v=1，矢量长度为 sqrt(2)，如果设置 `length_pre_unit=Pt(10)`，
        则箭头实际长度为 10*sqrt(2) pt，且不随画幅、Axes而变化，适用于需要在不同的子图中画出相同的
        矢量箭头。

        Draw a quiver plot by using the specified length unit. You can use 
        `length_pre_unit` to set the actual length of each unit of data. 
        For example, if u=1, v=1, the vector length is sqrt(2), and let 
        `length_pre_unit=Pt(10)`, then the actual length of the arrow is
        10*sqrt(2) pt, and it does not change with the figure or Axes.
        It is a convenient way to draw the same vector arrows in different subplots.

        Parameters
        ----------
        *args
            plt.quiver() 原始的位置参数, 例如 X, Y, U, V,

            the original positional arguments of plt.quiver(), such as X, Y, U, V,
        length_pre_unit : LenType, optional
            数据每单位对应的实际长度, 默认为 Pt(10)

            the actual length of each unit of data, by default Pt(10)
        width : LenType, optional
            箭头的宽度, 默认为 Pt(1)

            the width of the arrow, by default Pt(1)

        Returns
        -------
        Quiver
            Quiver 对象

            Quiver object
        """

        length_pre_unit, width = as_inch(self._figure, length_pre_unit, width)

        q = self.ax.quiver(*args, scale_units='inches',
                           scale=1/length_pre_unit.to_inch(),
                           units="inches", width=width.to_inch(), **kwargs)

        return q

    def draw_rectangle(self, left: float, right: float, bottom: float, top: float,
                segments: int = 20, transform=None, **kwargs) -> PathPatch:
        """画一个可以设置边分段数的矩形，在有地图投影的情况下会很有用。
        需要传入左、右、下、上的坐标值，还可以设置所有 Patch 的属性，例如
        edgecolor、facecolor、linewidth 等。

        Draw a rectangle with a specified number of segments. This is useful
        when there is a map projection. You need to pass in the left, right,
        bottom, and top coordinates, and you can also set all Patch properties,
        such as edgecolor, facecolor, linewidth, etc.

        Parameters
        ----------
        left : float
            左边的x值
        right : float
            右边的x值
        bottom : float
            下边的y值
        top : float
            上边的y值
        segments : int, optional
            每个边的分段数, by default 20
        transform : _type_, optional
            转换投影，如果没有设置此参数且 Ax 设置了地图投影，会自动设置。

        Returns
        -------
        PathPatch
            返回矩形的 PathPatch 对象
        """

        if transform is None and self._is_geo_axes:
            transform = ccrs.PlateCarree()._as_mpl_transform(self.ax)

        _left = [np.full(segments, left), np.linspace(top, bottom, segments)]
        _right = [np.full(segments, right), np.linspace(bottom, top, segments)]
        _bottom = [np.linspace(left, right, segments), np.full(segments, bottom)]
        _top = [np.linspace(right, left, segments), np.full(segments, top)]
        v = np.concatenate([_left, _bottom, _right, _top], axis=1)
        v = v.T

        path = Path(v, closed=True)
        path_patch = PathPatch(path, **kwargs)

        if transform is not None:
            path_patch.set_transform(transform)

        return self.ax.add_patch(path_patch)


    def map_base(self, 
                 lon: np.ndarray | tuple | list=None,
                 lat: np.ndarray | tuple | list=None,
                 lon_ticks: np.ndarray | list=None, 
                 lat_ticks: np.ndarray | list=None,
                 show_lon_label: bool=True, show_lat_label: bool=True,
                 draw_minor_tick: bool=True, draw_gridline: bool=True,
                 use_axes_ticks: bool=True, map_config: MapConfig=None,
                 draw_minor_gridline: bool=False,
                 ignore_frist_lon_tick_label: bool=False,
                 rotateo_lat_ticks: bool=False,):
        """绘制基本地图的底图、经纬刻度网格。最好在使用 ax.set_extent() 之后调用。
        该函数会自动计算经纬度的范围，并根据范围设置主刻度和次刻度，设置画图配置
        需要定义一个 MapConfig 对象并传入 map_config 参数。

        ```python
        ax = sp.ax
        ax.set_extent(70, 130, 0, 60)  # 设置地图范围
        sp.map_base()  # 绘制地图底图和经纬度刻度

        # 或者自定义配置
        config = MapConfig(coastline_width=1, gridline_color='red')
        sp.map_base(map_config=config)
        ```

        Parameters
        ----------
        lon, lat : np.ndarray | tuple | list, optional
            经纬度，可以传入一组经纬度的值（通常为数据的经纬度），将选取最大与最小值
            作为地图范围, 如果没有传入，则自动使用 Axes 的地图范围（需要在用
            set_extent() 之后调用）。

            longitude and latitude, you can pass in a set of longitude and latitude
            values (usually the longitude and latitude of the data), and the maximum
            and minimum values will be selected as the map range. If not passed in,
            the map range of the Axes will be used automatically (need to be called
            after set_extent()).

        lon_ticks, lat_ticks : np.ndarray | list, optional
            指定经纬度刻度的值，默认将根据 MapConfig 中的主刻度间隔生成。

            Specify the values of longitude and latitude ticks. By default, the
            main tick interval in MapConfig will be used to generate the ticks.

        show_lon_label, show_lat_label : bool, optional
            当前子图是否显示经纬度刻度标签，默认为 True。

            Whether to show the longitude and latitude tick labels in the current
            subplot, default is True.

        draw_minor_tick : bool, optional
            是否绘制次刻度，默认为 True，次刻度间隔在 MapConfig 中设置。

            Whether to draw minor ticks, default is True. The minor tick interval
            is set in MapConfig.

        draw_gridline : bool, optional
            是否绘制网格线，默认为 True，网格线的样式在 MapConfig 中设置。

            Whether to draw gridlines, default is True. The style of the gridline
            is set in MapConfig.

        use_axes_ticks : bool, optional
            是否使用 Axes 的刻度，默认为 True，设置为 False 时会使用网格线的刻度
            （需要绘制网格线）。

            Whether to use the Axes ticks, default is True. If set to False, the
            gridline ticks will be used (gridlines need to be drawn).

        map_config : MapConfig, optional
            配置对象，如果没有传入，则使用默认配置。

            Configuration object. If not passed in, the default configuration will
            be used.

        draw_minor_gridline : bool, optional
            是否绘制次网格线，默认为 False。

            Whether to draw minor gridlines, default is False.

        ignore_frist_lon_tick_label : bool, optional
            是否忽略第一个经度刻度标签，默认为 False，在密集排列时有用。

            Whether to ignore the first longitude tick label, default is False.
            This is useful when the ticks are densely arranged.
        """

        # 如果没有指定 mapConfig，则使用默认配置
        if map_config is None:
            c = MapConfig()
        else:
            c = map_config

        # 绘制海岸线
        if c.coastline:
            self.ax.coastlines(linewidth=c.coastline_width, color=c.coastline_color,
                               **c.coastline_kwargs)

        # 填充陆地
        if c.fill_land:
            import cartopy.feature as cfeature
            self.ax.add_feature(cfeature.LAND, facecolor=c.land_color)

        from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
        from matplotlib.ticker import MultipleLocator

        # 如果不指定刻度，则用主刻度间隔生成
        extent = [0, 0, 0, 0]
        ax_extent = self.ax.get_extent()

        if lon_ticks is None:
            if lon is not None:
                lon = np.asarray(lon)
                extent[0] = np.min(lon)
                extent[1] = np.max(lon)
            else:
                extent[0] = ax_extent[0]
                extent[1] = ax_extent[1]

            all_lon_ticks = np.arange(-180, 361, c.lon_major_interval)

            lon_ticks = all_lon_ticks[(all_lon_ticks >= extent[0]-0.1) &
                                        (all_lon_ticks <= extent[1]+0.1)]

        if lat_ticks is None:
            if lat is not None:
                lat = np.asarray(lat)
                extent[2] = np.min(lat)
                extent[3] = np.max(lat)
            else:
                extent[2] = ax_extent[2]
                extent[3] = ax_extent[3]
            all_lat_ticks = np.arange(-80, 81, c.lat_major_interval)
            lat_ticks = all_lat_ticks[(all_lat_ticks >= extent[2]) &
                                    (all_lat_ticks <= extent[3])]

        if ignore_frist_lon_tick_label:
            lon_ticks = lon_ticks[1:]

        self.ax.xaxis.set_major_formatter(LongitudeFormatter())
        self.ax.yaxis.set_major_formatter(LatitudeFormatter())
        self.ax.set_xticks(lon_ticks, crs=self.ax.projection)
        self.ax.set_yticks(lat_ticks, crs=self.ax.projection)

        if not show_lon_label:
            self.ax.set_xticklabels([])
        if not show_lat_label:
            self.ax.set_yticklabels([])
        
        if rotateo_lat_ticks:
            self.ax.yaxis.set_tick_params(labelrotation=90)
            lbs = self.ax.yaxis.get_ticklabels()
            for lb in lbs:
                lb.set_verticalalignment('center')

        if draw_minor_tick:
            self.ax.xaxis.set_minor_locator(MultipleLocator(c.lon_minor_interval))
            self.ax.yaxis.set_minor_locator(MultipleLocator(c.lat_minor_interval))

        if draw_gridline:
            if draw_minor_gridline:
                lonTicks = np.arange(lon_ticks[0] - c.lon_minor_interval, 
                                     lon_ticks[-1] + 2 * c.lon_major_interval, 
                                     c.lon_minor_interval)
                latTicks = np.arange(lat_ticks[0] - c.lat_minor_interval,
                                     lat_ticks[-1] + 2 * c.lat_major_interval,
                                     c.latMinorInterval)

            gl = self.ax.gridlines(crs=self.ax.projection, 
                                   draw_labels=not use_axes_ticks,
                                   xlocs=lon_ticks, ylocs=lat_ticks, 
                                   linewidth=c.gridline_width, 
                                   color=c.gridline_color,
                                   linestyle=c.gridline_style,
                                   zorder=c.gridline_zorder,
                                   **c.gridline_kwargs, )
            gl.top_labels = False
            gl.right_labels = False

            if use_axes_ticks:
                gl.left_labels = False
                gl.bottom_labels = False
            else:
                gl.left_labels = show_lat_label
                gl.bottom_labels = show_lon_label

    def quiver_legend(self, Q: Quiver, U: float, label: str, angle: float=0,
                    coordinate: Literal['axes', 'figure', 'data', 'inches'] = 'axes',
                    labelpos: Literal['N', 'E', 'S', 'W'] = 'E',
                    labelsep: LenType=Pt(4),
                    loc: LocType = 'upper right',
                    bbox_to_anchor: tuple = None, color: any=None,
                    pad: LenType=Pt(5), borderpad: LenType=Pt(5),
                    frameon: bool=True, fontproperties=None,
                    style: Literal['default', 'legend'] = 'default',
                    **kwargs):
        """
        添加一个 Quiver 箭头图例，与 quiverkey 不同的是，像 legend 一样有边框
        且可以设置对齐方式。需要注意的是，使用此图例，需要在创建 Figure 时设置
        与保存时的 DPI 一致，否则图例的大小会不一致。
        参数融合了 quiverkey 和 legend 的参数。

        Parameters
        ----------
        Q : Quiver
            `~.quiver()` 返回的对象。

            the return object of `~.quiver()`
        U : float, optional
            图例中箭头的长度，默认为 1。

            The length of the arrow in the legend, default is 1.
        label : str, optional
            图例中的文字说明。 The text in the legend.
        angle : float, optional
            箭头的角度，默认为 0。

            The angle of the arrow, default is 0.
        coordinate : Literal['axes', 'figure', 'data', 'inches'], optional
            图例所在的坐标系，默认为 'axes'。

            The coordinate system of the legend, default is 'axes'.
        labelpos : Literal['N', 'E', 'S', 'W'], optional
            文字在箭头的哪个方向，与 quiverkey 一致，默认为 'E'。

            The position of the text relative to the arrow, default is 'E'.
        labelsep : LenType, optional
            文字与箭头之间的间隔，单位为 pt，默认为 4 pt。

            The distance between the text and the arrow, default is 4 pt.
        loc : LocType, optional
            图例的位置，默认为 'upper right'。

            The location of the legend, default is 'upper right'.
        bbox_to_anchor : tuple, optional
            图例的锚点位置，默认为 None。

            The anchor point of the legend, default is None.
        color : any, optional
            箭头的颜色，默认跟随 quiver 的颜色。

            The color of the arrow, default is the same as the quiver.
        pad : LenType, optional
            图例的内边距，单位为字体大小，默认为 5 pt。

            The padding of the legend, default is 5 pt.
        borderpad : LenType, optional
            图例与锚点之间的间距，单位为字体大小，默认为 5 pt。

            The padding between the legend and the anchor point, default is 5 pt.
        frameon : bool, optional
            是否显示边框，默认为 True。

            Whether to show the border, default is True.
        fontproperties : any, optional
            字体属性，默认为 None。

            Font properties, default is None.
        style : Literal['default', 'legend'], optional
            预设样式，默认为白底无边框，可以设置为 'legend'，类似图例的样式。

            Preset style, default is 'default', can be set to 'legend' for a 
            legend-like style.

        **kwargs : any, optional
            其他设置边框的参数，传递给 `FancyBboxPatch`。

            Other parameters for the border, passed to `FancyBboxPatch`.

        """
        from ._quiver import QuiverLegend
        qk = QuiverLegend(Q, U, label, angle, coordinate, labelpos, labelsep,
                            loc, bbox_to_anchor, color, pad, borderpad, frameon,
                            fontproperties, style, **kwargs)
        self.ax.add_artist(qk)
        return qk



    def barbs(self, x: np.ndarray | DataArray, y: np.ndarray | DataArray,
              u: np.ndarray | DataArray, v: np.ndarray | DataArray,
              low_res: int=1, barb_increments: dict=None, 
              transform=ccrs.PlateCarree(), length: LenType=Pt(5.5),
              linewidth: LenType=Pt(0.5), color: any='black',
              hide_empty: bool=True, **kwargs):
        """绘制风羽，默认配置好了短杆为 2，长杆为 4，三角为 20。支持降采样和
        隐藏空的风羽。

        Parameters
        ----------
        x, y, u, v : np.ndarray | DataArray
            风羽的坐标和风速，与 `plt.barbs()` 一致。

            The coordinates and wind speed of the barbs, consistent with `plt.barbs()`.
        low_res : int, optional
            降采样间隔，1为不降采样。

            The downsampling interval, 1 means no downsampling.
        barb_increments : dict, optional
            自定义风羽长短杆的值，与 `plt.barbs()` 一致。

            Custom values for the short and long barbs, consistent with `plt.barbs()`.
        transform : _type_, optional
            地图投影，默认为 PlateCarree。

            The map projection, default is PlateCarree.
        length, linewidth: LenType, optional
            风羽的长度和线宽，单位为 pt。

            The length and line width of the barbs, in pt.
        color : str, optional
            风羽的颜色，默认为黑色。

            The color of the barbs, default is black.
        hide_empty : bool, optional
            是否隐藏空的风羽，默认为 True。

            Whether to hide empty barbs, default is True.

        Returns
        -------
        Barbs
            返回 `plt.barbs()` 的返回对象。

        """

        x = np.asarray(x)
        y = np.asarray(y)
        u = np.asarray(u)
        v = np.asarray(v)

        length, linewidth = as_unit_value("pt", length, linewidth,
                                          fig=self._figure)

        def _low_res(v: np.ndarray, low_res: int):
            if len(v.shape) == 1:
                return v[::low_res]
            elif len(v.shape) == 2:
                return v[::low_res, ::low_res]
            else:
                raise ValueError("arrays must be 1D or 2D")

        if barb_increments is None:
            barb_increments = dict(half=2, full=4, flag=20)

        if hide_empty:
            sizes = {"emptybarb": 0}
        else:
            if sizes is None:
                sizes = {"emptybarb": 0.5}

        if "sizes" in kwargs:
            sizes.update(kwargs.pop("sizes"))

        return self.ax.barbs(
                    _low_res(x, low_res), _low_res(y, low_res),
                    _low_res(u, low_res), _low_res(v, low_res),
                    barb_increments=barb_increments, transform=transform,
                    length=length, linewidth=linewidth, color=color,
                    sizes=sizes, **kwargs)


    def rotate_y_ticks(self, deg: float=90) -> None:
        """旋转y轴刻度标签，默认旋转90度，并垂直居中。

        rotate the y-axis tick labels by a specified degree and center them vertically. Default is 90 degrees.

        Parameters
        ----------
        deg : float, optional
            逆时针旋转的角度，默认为 90。

            The angle of counterclockwise rotation, default is 90.
        """
        self.ax.yaxis.set_tick_params(labelrotation=deg)
        lbs = self.ax.yaxis.get_ticklabels()
        for lb in lbs:
            lb.set_verticalalignment('center')


    def format_ticks_to_lon_lat(self, which: Literal['both', 'lon', 'lat']='both'
                                )-> None:
        """将刻度格式化为经纬度格式，使用 cartopy 的 LongitudeFormatter 和
        LatitudeFormatter。可以选择只格式化经度或纬度。
        只支持 x轴为经度，y轴为纬度。

        Format the ticks to longitude and latitude format, using cartopy's
        LongitudeFormatter and LatitudeFormatter. You can choose to format only
        the longitude or latitude. Only supports x-axis as longitude and y-axis
        as latitude.

        Parameters
        ----------
        which : Literal['both', 'lon', 'lat'], optional
            需要格式化的刻度，默认为 'both'。
        """
        from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
        if which in ['both', 'lon']:
            self.ax.xaxis.set_major_formatter(LongitudeFormatter())
        if which in ['both', 'lat']:
            self.ax.yaxis.set_major_formatter(LatitudeFormatter())

    def set_tick_locator(self, x_major: float=None, y_major: float=None,
                         x_minor: float=None, y_minor: float=None,
                         ) -> None:
        """一个方便设置主副刻度间隔的方法，等价于：

        ```python
        from matplotlib.ticker import MultipleLocator
        ax.xaxis.set_major_locator(MultipleLocator(x_major))
        ax.yaxis.set_major_locator(MultipleLocator(y_major))
        ax.xaxis.set_minor_locator(MultipleLocator(x_minor))
        ax.yaxis.set_minor_locator(MultipleLocator(y_minor))
        ```

        A convenient method to set the major and minor tick intervals, which
        is equivalent to above code.

        Parameters
        ----------
        x_major, y_major, x_minor, y_minor : float, optional
            主刻度和次刻度的间隔，默认为 None，即不设置。
        """
        from matplotlib.ticker import MultipleLocator
        if x_major is not None:
            self.ax.xaxis.set_major_locator(MultipleLocator(x_major))
        if y_major is not None:
            self.ax.yaxis.set_major_locator(MultipleLocator(y_major))
        if x_minor is not None:
            self.ax.xaxis.set_minor_locator(MultipleLocator(x_minor))
        if y_minor is not None:
            self.ax.yaxis.set_minor_locator(MultipleLocator(y_minor))


    def set_time_ticks(self, which: Literal['major', 'minor'],
                       axis: Literal['x', 'y'], interval: float,
                       unit: Literal['ms', 's', 'minute', 'h', 'd',
                                     'month', 'y', 'weekday'],
                       format: str='%Y-%m-%d %H:%M:%S', tz:str=None):
        """方便设置时间刻度的方法，可设置刻度间隔与格式。

        A convenient method to set time ticks, which can set the tick interval
        and format.

        Parameters
        ----------
        which : Literal['major', 'minor']
            设置刻度的类型，主刻度或次刻度。

            The type of tick to set, major or minor.
        axis : Literal['x', 'y']
            设置的轴。

            The axis to set the ticks on, either 'x' or 'y'.
        interval : float
            刻度间隔的数值。

            The numerical value of the tick interval.
        unit : Literal['ms', 's', 'minute', 'h', 'd', 'month', 'y', 'weekday']
            刻度间隔的单位，支持毫秒、秒、分钟、小时、天、月、年和星期几。

            The unit of the tick interval, supports milliseconds, seconds,
            minutes, hours, days, months, years and weekdays.
        format : str, optional
            设置主刻度的格式，默认为 '%Y-%m-%d %H:%M:%S'。

            The format of the ticks, by default '%Y-%m-%d %H:%M:%S'.
        tz : str, optional
            设置时区，默认为 None。

            The timezone, by default None.

        """
        from matplotlib.dates import DateFormatter
        if unit == 'ms':
            from matplotlib.dates import MicrosecondLocator
            loc = MicrosecondLocator(interval=interval, tz=tz)
        elif unit == 's':
            from matplotlib.dates import SecondLocator
            loc = SecondLocator(interval=interval, tz=tz)
        elif unit == 'minute':
            from matplotlib.dates import MinuteLocator
            loc = MinuteLocator(interval=interval, tz=tz)
        elif unit == 'h':
            from matplotlib.dates import HourLocator
            loc = HourLocator(interval=interval, tz=tz)
        elif unit == 'd':
            from matplotlib.dates import DayLocator
            loc = DayLocator(interval=interval, tz=tz)
        elif unit == 'month':
            from matplotlib.dates import MonthLocator
            loc = MonthLocator(interval=interval, tz=tz)
        elif unit == 'y':
            from matplotlib.dates import YearLocator
            loc = YearLocator(interval=interval, tz=tz)
        elif unit == 'weekday':
            from matplotlib.dates import WeekdayLocator
            loc = WeekdayLocator(interval=interval, tz=tz)
        else:
            raise ValueError(f"Unknown unit: {unit}")

        if axis == 'x':
            ax = self.ax.xaxis
        elif axis == 'y':
            ax = self.ax.yaxis
        else:
            raise ValueError(f"Unknown axis: {axis}")

        if which == 'major':
            ax.set_major_locator(loc)
            ax.set_major_formatter(DateFormatter(format, tz))
        elif which == 'minor':
            ax.set_minor_locator(loc)
        else:
            raise ValueError(f"Unknown which: {which}")


    def add_shapefile(self):
        pass

    def clip_by_shapefile(self):
        pass


    def stream_quiver(self, X: np.ndarray | DataArray, Y: np.ndarray | DataArray,
                      U: np.ndarray | DataArray, V: np.ndarray | DataArray,
                      scale: float=1, grid_num: int | tuple[int, int]=20,
                      head_length: float = 0.3, arrow_angle: float = 30,
                      **kwargs):
        """绘制流线箭头图，类似于 `plt.streamplot()`，但使用箭头表示流向。"""
        from ._quiver import StreamQuiver
        sq = StreamQuiver(X, Y, U, V, scale=scale, grid_num=grid_num, 
                          head_length=head_length, arrow_angle=arrow_angle,
                          **kwargs)
        self.ax.add_collection(sq)
        return sq




class Subplot(AxesTools):

    def __init__(self, ax: Axes, row: int, col: int, nrows: int, ncols: int,
                 subplot_spec: SubplotSpec):
        super().__init__(ax)

        self.row = row
        """行索引"""
        self.col = col
        """列索引"""
        self.nrows = nrows
        """总行数"""
        self.ncols = ncols
        """总列数"""
        self.subplot_spec = subplot_spec
        """子图的 SubplotSpec 对象"""

        self.row_index = col + row * ncols
        """以行优先的索引"""
        self.col_index = row + col * nrows
        """以列优先的索引"""
